package vista.objetos;

import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.awt.GLJPanel;
import javax.swing.JFrame;

import controlador.ContenedorDeErrores;
import controlador.ControladorEditor;

import eventos.EventoEtiquetaDeGraficable;
import eventos.abrir.EventoEditarVentanaJerarquia;

import vista.Tema;
import vista.paneles.hojaDibujo.HojaDeDibujo;
import vista.paneles.hojaDibujo.UtilHojaDeDibujo;
import vista.treeDiagrama.NodoJerarquia;
import vista.treeDiagrama.NodoTreeDiagrama;

import logica.Entidad;
import logica.Jerarquia;
import logica.JerarquiaException;
import logica.ObjetoDiagrama;
import logica.persistencia.GraficableConEtiquetaPersistente;
import logica.persistencia.JerarquiaGraficablePersistente;
import memento.IMemento;
import memento.MementoCompuesto;
import memento.MementoJerarquiaGraficable;

public class JerarquiaGraficable extends GraficableConEtiqueta{

	private Jerarquia jerarquiaDecorada;
	
	private EntidadGraficable entidadPadre;
	private List<EntidadGraficable> entidadesHijas = new LinkedList<EntidadGraficable>();
	
	private Point v1 = new Point();
	private Point v2 = new Point();
	private Point v3 = new Point();
	
	public JerarquiaGraficable(Jerarquia jerarquia)
	{
		this.jerarquiaDecorada = jerarquia;
		this.jerarquiaDecorada.addObserver(this);
		this.jerarquiaDecorada.getGraficables().add(this);
		
		inicializarEtiqueta();
	}

	public void inicializarPosicionPunto(int x, int y) {
		super.inicializarPosicionPunto(x, y); 
		super.setUbicacionEtiqueta(this.getPunto().x - this.getEtiqueta().getWidth()/2, this.getPunto().y - this.getEtiqueta().getHeight()/2);
	}
	
	private void inicializarEtiqueta() 
	{
		String texto = "<html> <center> ";
		
		texto += this.jerarquiaDecorada.getNombre();
		texto += ("<br>");
		texto += ("("+this.jerarquiaDecorada.getEsParticionCompleta()+","+this.jerarquiaDecorada.getHaySolapamiento()+")");
		texto += ("</center></html>");

		this.getEtiqueta().setText(texto);
		this.getEtiqueta().setSize(50,50);
	}
	
	public void inicializarPosiciones()
	{
		int x = 0, y = 0, xPadre = entidadPadre.getPunto().x, 
			yPadre = entidadPadre.getPunto().y;
		
		for(EntidadGraficable entidadHija: entidadesHijas)
		{
			x += (entidadHija.getPunto().x + xPadre)/2;
			y += (entidadHija.getPunto().y + yPadre)/2;
		}
		 
		x /= this.getCantidadDeHijas();
		y /= this.getCantidadDeHijas();
		
		this.getPunto().setLocation(x, y);
		this.setUbicacionEtiqueta(x, y);
		
	}

	public String toString() {
		return this.jerarquiaDecorada.getNombre();
	}

	@Override
	public ObjetoDiagrama getObjetoDiagramaDecorado(){
		return this.jerarquiaDecorada;
	}
	
	@Override
	public NodoTreeDiagrama getNodoTreeDiagrama() {
		return new NodoJerarquia(this);
	}
	
	@Override
	public void setUbicacionEtiqueta(int x, int y)
	{
		super.setUbicacionEtiqueta(x, y);
		this.getPunto().setLocation(x + this.getEtiqueta().getWidth()/2, y + this.getEtiqueta().getHeight()/2);
	}

	@Override
	public void agregarseAlCanvas(GLJPanel panel, Font fuente, Color color)
	{
		super.agregarseAlCanvas(panel, fuente, color);
		super.setUbicacionEtiqueta(this.getPunto().x - this.getEtiqueta().getWidth()/2, this.getPunto().y - this.getEtiqueta().getHeight()/2);
	}

	@Override
	public void dibujarse(Object areaDeDibujo) 
	{
		GLAutoDrawable gLDrawable = (GLAutoDrawable) areaDeDibujo; 
		GL2 gl = gLDrawable.getGL().getGL2();
		
		float[] coordMcentroEntidadPadre = UtilHojaDeDibujo.getCoordMundo(gl,this.getEntidadPadre().getPunto());
		float[] coordMcentroJerarquia = UtilHojaDeDibujo.getCoordMundo(gl,this.getPunto());
		float[] coordMcentroEntidadHija;
		float[] vert1, vert2, vert3;
		Iterator<EntidadGraficable> itEntHija = this.getEntidadesEspecializadas().iterator();

		v1.x = this.getPunto().x - this.getEtiqueta().getWidth();
		v2.x = this.getPunto().x + this.getEtiqueta().getWidth();
		v3.x = this.getPunto().x;
		v1.y = this.getPunto().y - this.getEtiqueta().getHeight()/2;
		v2.y = this.getPunto().y - this.getEtiqueta().getHeight()/2;
		v3.y = this.getPunto().y + this.getEtiqueta().getHeight()/2;
		
		vert1 = UtilHojaDeDibujo.getCoordMundo(gl,v1);
		vert2 = UtilHojaDeDibujo.getCoordMundo(gl,v2);
		vert3 = UtilHojaDeDibujo.getCoordMundo(gl,v3);
		
		gl.glBegin(GL2.GL_LINES);

			//linea que une entidad padre con dibujo de jerarquia
			gl.glLineWidth(3);
			gl.glColor3fv(Tema.getInstancia().getLineaColor());
			gl.glVertex2f(coordMcentroEntidadPadre[0], coordMcentroEntidadPadre[1]);
			gl.glVertex2f(coordMcentroJerarquia[0], coordMcentroJerarquia[1]);

			gl.glLineWidth(1);
			gl.glColor3f(0.85f, 0.85f, 0.85f);
			//linea que une entidades hijas con dibujo de jerarquia
			while (itEntHija.hasNext())
			{
				EntidadGraficable entidadGraf = itEntHija.next();
				coordMcentroEntidadHija = UtilHojaDeDibujo.getCoordMundo(gl, entidadGraf.getPunto());
				
				gl.glVertex2f(coordMcentroJerarquia[0], coordMcentroJerarquia[1]);
				gl.glVertex2f(coordMcentroEntidadHija[0], coordMcentroEntidadHija[1]); 
			}			
		gl.glEnd();

		gl.glBegin(GL2.GL_TRIANGLES);
			//dibujo de la jerarquia, el triangulo
			gl.glColor3fv(Tema.getInstancia().getJerarquiaColorSuperior());
			gl.glVertex2f(vert1[0], vert1[1]);
			gl.glVertex2f(vert2[0], vert2[1]);
			gl.glColor3fv(Tema.getInstancia().getJerarquiaColorInferior());
			gl.glVertex2f(vert3[0], vert3[1]);
		gl.glEnd();		
	}

	public int getCantidadDeHijas() {
		return this.getEntidadesEspecializadas().size();
	}

	@Override
	public boolean esElGraficableDe(ObjetoDiagrama objeto) {
		return false;
	}

	@Override
	public void update(Observable arg0, Object arg1) {
		inicializarEtiqueta();
	}

	public EntidadGraficable getEntidadPadre() {
		return entidadPadre;
	}

	public void setEntidadPadre(EntidadGraficable entidadPadre) {
		this.entidadPadre = entidadPadre;
	}

	public List<EntidadGraficable> getEntidadesEspecializadas() {
		return entidadesHijas;
	}

	public void setEntidadesEspecializadas(List<EntidadGraficable> entidadesHijas) {
		this.entidadesHijas = entidadesHijas;
	}

	@Override
	protected EventoEtiquetaDeGraficable getEvento(HojaDeDibujo hoja) {
		return new EventoEtiquetaDeGraficable(hoja, this);
	}

	@Override
	public MouseAdapter getEventoEditarGraficable(JFrame ventanaPadre) {
		return new EventoEditarVentanaJerarquia(ventanaPadre, ControladorEditor.getInstancia(), this);
	}

	@Override
	/*
	 *  copia:
	 *  	- datos de la jerarquía lógica contenida
	 *  	- datos de la parte gráfica
	 *  	- graficable del padre
	 *  	- graficable de los hijos directos.
	 *  
	 *  Si una de las entidades a copiar es débil. Se copiará también la relación correspondiente.
	 *  
	 *  chequea existencia de las entidades en el contenedor. Agrega lo que no esté en el contenedor
	 *  
	 *  retorna la copia
	 */
	public GraficableConEtiqueta copiarse(IContenedorDeGraficable contenedor)
	{
		EntidadGraficable padreCopia = (EntidadGraficable) this.getEntidadPadre().copiarse(contenedor);
		List<EntidadGraficable> hijasCopia = this.copiarEntidadesHijas(contenedor);
		JerarquiaGraficable jerarquiaCopia = new JerarquiaGraficable(jerarquiaDecorada);
		
		jerarquiaCopia.getPunto().setLocation(this.getPunto());
		jerarquiaCopia.setEntidadPadre(padreCopia);
		jerarquiaCopia.setEntidadesEspecializadas(hijasCopia);		

		contenedor.agregarGraficable(jerarquiaCopia);
		//contenedor.agregarObjeto(jerarquiaDecorada);
		
		return jerarquiaCopia;
	}

	private List<EntidadGraficable> copiarEntidadesHijas(IContenedorDeGraficable contenedor)
	{
		List<EntidadGraficable> hijas = new LinkedList<>();
		
		for (EntidadGraficable entidadHija: this.getEntidadesEspecializadas())
			hijas.add((EntidadGraficable)entidadHija.copiarse(contenedor));
		
		return hijas;
	}

	@Override
	public GraficableConEtiquetaPersistente getGraficableConEtiquetaPersistente() {
		return new JerarquiaGraficablePersistente(this);
	}

	@Override
	/*
	 * retorna true si la tiene como entidad padre o hija
	 */
	public boolean tieneLaEntidad(EntidadGraficable entidadGraficable) 
	{
		if (this.getEntidadPadre() == entidadGraficable)
			return true;
		
		for (EntidadGraficable entidadGraf: this.getEntidadesEspecializadas())
		{
			if (entidadGraf == entidadGraficable)
				return true;
		}

		return false;
	}
	
	@Override
	/*
	 * dada una entidad que se quiere borrar determina si se puede borrar la jerarquía graficable.
	 * 		No se puede borrar si no tiene esa entidad
	 * 		No se puede borra si la tiene, pero tiene más de un hijo
	 * 
	 * 		Se puede borrar, (se debe borrar) si la entidad es la entidad padre o la única hija.
	 */
	public boolean sePuedeBorrar(EntidadGraficable entidadGraficable)
	{
		if (!this.tieneLaEntidad(entidadGraficable))
			return false;
		
		if (this.getEntidadPadre() == entidadGraficable)
			return true;
		
		if (getCantidadDeHijas() > 1)
			return false;
		
		return true;
	}

	/* 
	 * no hay restricción para borrar una jerarquía
	 */
	public void borrarseDelDiagrama()
	{
		if(this.getDiagrama() == null)
			return;
		
		try {
			this.getDiagrama().borrarGraficable(this);
		} catch (NoEsObjetoDiagramableException e) {//no debería pasar.
			e.printStackTrace();
		}
	}
	
	@Override
	/*
	 * si borro una entidad tiene que ser de sus hijas, ya que el padre no se puede editar, ni borrar
	 * si se elimina el padre, se debe eliminar la jerarquía
	 * 
	 * se actualizan todas las vistas de la jerarquía
	 */
	public void eliminarEntidad(EntidadGraficable entidadGraficable) throws GraficableException
	{
		Iterator<EntidadGraficable> itEntidad;
		List<Entidad> listaEntidadEspecializadas = new LinkedList<>();
		JerarquiaGraficable jerarquia;

		for(GraficableConEtiqueta graficable: jerarquiaDecorada.getGraficables())
		{	
			jerarquia = (JerarquiaGraficable) graficable;
			itEntidad = jerarquia.getEntidadesEspecializadas().iterator();
			
			while(itEntidad.hasNext())	// busco y borro la entidad a sacar
			{
				if (itEntidad.next().getObjetoDiagramaDecorado() == entidadGraficable.getObjetoDiagramaDecorado())
				{
					itEntidad.remove();
					break;
				}
			}
		}
		//vuelvo a setear la list de entidades en la lógica
		
		for(EntidadGraficable entidad: this.getEntidadesEspecializadas())
			listaEntidadEspecializadas.add((Entidad) entidad.getObjetoDiagramaDecorado());
		
		try {
			jerarquiaDecorada.setEntidadEspecilizada(listaEntidadEspecializadas);
		} catch (JerarquiaException e) {
			throw new GraficableException(e.getMessage());
		}
	}

	@Override
	public  IMemento getMementoGenerico() {
		
		if (this.getObjetoDiagramaDecorado().getGraficables().size() > 1)
			return new MementoCompuesto(this);
		
		return this.getMementoEspecializado(); 
	}
	
	@Override
	public IMemento getMementoEspecializado() {
		return new MementoJerarquiaGraficable(this);
	}

	@Override
	public void validar(ContenedorDeErrores errores)
	{
		if (jerarquiaDecorada.getEntidadesEspecializadas().size() != this.getCantidadDeHijas())
			errores.agregarError("Diagrama graficable: <<" +  this.getDiagrama().getDiagrama().getNombre() + ">> - La cantidad de entidades especializadas de la jerarquía graficable <<"+ jerarquiaDecorada.getNombre() + ">> no coincide en el modelo lógico (" + jerarquiaDecorada.getEntidadesEspecializadas().size() + ") y gráfico (" + this.getCantidadDeHijas() + ").");
		
		for (Entidad entidad: jerarquiaDecorada.getEntidadesEspecializadas())
			verificarEntidadesEspecializadas(entidad, errores);
		
		if (jerarquiaDecorada.getEntidadPadre() != this.getEntidadPadre().getObjetoDiagramaDecorado())
			errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - En la jerarquía graficable <<" + jerarquiaDecorada.getNombre() + ">> la entidad padre no coincide en el modelo gráfico (" + this.getEntidadPadre().getObjetoDiagramaDecorado().getNombre() + ") y lógico (" + jerarquiaDecorada.getEntidadPadre() + ").");
		
		this.jerarquiaConAtributosRepetidos(errores);
	}

	private void verificarEntidadesEspecializadas(Entidad entidad, ContenedorDeErrores errores) 
	{
		for (EntidadGraficable entidadGraficable: this.getEntidadesEspecializadas())
			if (entidad == entidadGraficable.getObjetoDiagramaDecorado())
				return;
		
		errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - La jerarquía graficable <<" + jerarquiaDecorada.getNombre() + ">> no tiene una entidad graficable que represente a la entidad lógica <<" + entidad.getNombre() +">>");
	}

	private void jerarquiaConAtributosRepetidos(ContenedorDeErrores errores) 
	{
		List<AtributoGraficable> atributos = this.getEntidadPadre().getAtributos();
		
		for ( EntidadGraficable entidadHija: this.getEntidadesEspecializadas() )
		{
			for ( AtributoGraficable atributoDelPadre: atributos )
			{
				for ( AtributoGraficable atributoDelHijo: entidadHija.getAtributos() )
					if ( atributoDelPadre.getAtributo().getNombre().compareToIgnoreCase( atributoDelHijo.getAtributo().getNombre()) == 0 )
						errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - En la jerarquía graficable <<" + jerarquiaDecorada.getNombre() + ">> la entidad hija <<" + entidadHija.getObjetoDiagramaDecorado().getNombre() + ">> tiene un atributo con el mismo nombre que el de su padre: <<" + atributoDelPadre.getAtributo().getNombre() +">>");
			}
		}
	}
	
	/*
	 * si ambas jerarquias tienen el mismo padre, no retorna esa entidad como entidad
	 * común ya que dos jerarquias distintas pueden tener un mismo padre.
	 */
	public List<EntidadGraficable> entidadesEnComun(JerarquiaGraficable otraJerarquia)
	{
		List<EntidadGraficable> entidadesEnComun = new LinkedList<>();
		
		if ( otraJerarquia.tieneComoHijaA(getEntidadPadre()) )
			entidadesEnComun.add(getEntidadPadre());
		
		for ( EntidadGraficable entidadOtra: otraJerarquia.getEntidadesEspecializadas() )
			if ( this.tieneLaEntidad(entidadOtra) )
				entidadesEnComun.add(entidadOtra);
		
		return entidadesEnComun;
	}
	
	public boolean tieneComoHijaA(EntidadGraficable entidadGraficable) 
	{
		for (EntidadGraficable entidadGraf: this.getEntidadesEspecializadas())
			if (entidadGraf == entidadGraficable)
				return true;

		return false;
	}
}
